package com.zzyl.serve.service.impl;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.TimeUnit;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.zzyl.common.constant.CacheConstants;
import com.zzyl.common.utils.DateUtils;
import com.zzyl.common.utils.StringUtils;
import com.zzyl.common.utils.bean.BeanUtils;
import com.zzyl.serve.domain.AlertData;
import com.zzyl.serve.domain.Bed;
import com.zzyl.serve.domain.DeviceData;
import com.zzyl.serve.mapper.DeviceDataMapper;
import com.zzyl.serve.mapper.DeviceMapper;
import com.zzyl.serve.mapper.NursingElderMapper;
import com.zzyl.serve.service.IAlertDataService;
import com.zzyl.system.mapper.SysUserRoleMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import com.zzyl.serve.mapper.AlertRuleMapper;
import com.zzyl.serve.domain.AlertRule;
import com.zzyl.serve.service.IAlertRuleService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import javax.annotation.Resource;

            /**
             * 报警规则Service业务层处理
             *
             * @author liuyp
             * @date 2025-08-26
             */
    @Service
    public class AlertRuleServiceImpl extends ServiceImpl<AlertRuleMapper, AlertRule> implements IAlertRuleService {
            @Autowired
            private AlertRuleMapper alertRuleMapper;
            @Autowired
            private RedisTemplate<Object, Object> redisTemplate;

            @Autowired
            private DeviceMapper deviceMapper;
            @Autowired
            private IAlertDataService alertDataService;
            @Autowired
            private SysUserRoleMapper userRoleMapper;
            @Autowired
            private NursingElderMapper nursingElderMapper;
            @Autowired
            private DeviceDataMapper deviceDataMapper;


                /**
             * 查询报警规则
             *
             * @param id 报警规则主键
             * @return 报警规则
             */
            @Override
            public AlertRule selectAlertRuleById(Long id) {
            return alertRuleMapper.selectById(id);
        }

            /**
             * 查询报警规则列表
             *
             * @param alertRule 报警规则
             * @return 报警规则
             */
            @Override
            public List<AlertRule> selectAlertRuleList(AlertRule alertRule) {
            return alertRuleMapper.selectAlertRuleList(alertRule);
        }

            /**
             * 新增报警规则
             *
             * @param alertRule 报警规则
             * @return 结果
             */
            @Override
            public int insertAlertRule(AlertRule alertRule) {
            return alertRuleMapper.insert(alertRule);
        }

            /**
             * 修改报警规则
             *
             * @param alertRule 报警规则
             * @return 结果
             */
            @Override
            public int updateAlertRule(AlertRule alertRule) {
            return alertRuleMapper.updateById(alertRule);
        }

            /**
             * 批量删除报警规则
             *
             * @param ids 需要删除的报警规则主键
             * @return 结果
             */
            @Override
            public int deleteAlertRuleByIds(Long[] ids) {
            return alertRuleMapper.deleteBatchIds(Arrays.asList(ids));
        }

            /**
             * 删除报警规则信息
             *
             * @param id 报警规则主键
             * @return 结果
             */
            @Override
            public int deleteAlertRuleById(Long id) {
            return alertRuleMapper.deleteById(id);
        }

            // 报警过滤
            @Override
            public void alertFilter() {
            //1.如果没有已经启用报警规则直接结束
            long count = count(Wrappers.<AlertRule>lambdaQuery()
                    .eq(AlertRule::getStatus, 1));
            if (count == 0) {
                return;
            }
            //2.如果查不到设备上报的数据直接结束
            List<Object> list = redisTemplate.opsForHash().values(CacheConstants.IOT_DEVICE_LAST_DATA);
            if (ObjectUtil.isEmpty(list)) {
                return;
            }
            //3.解析上报的数据，转换成 List<DeviceData>列表
            List<DeviceData> deviceDataList = new ArrayList<>();
            list.forEach(o -> deviceDataList.addAll(JSONUtil.toList(o.toString(), DeviceData.class)));

            //4.处理设备上报的每条数据
            deviceDataList.forEach(deviceData -> alertFilter(deviceData));
        }


        //5.如果设备上报数据的时间已经过了一分钟直接结束
            private void alertFilter(DeviceData deviceData) {
            long between = LocalDateTimeUtil.between(deviceData.getAlarmTime(), LocalDateTime.now(), ChronoUnit.SECONDS);
            if (between > 60) {
                return;
            }

                LambdaQueryWrapper<AlertRule> wrapper = Wrappers.<AlertRule>lambdaQuery()
                        // 是否有针对产品的规则
                        .eq(AlertRule::getProductKey, deviceData.getProductKey())
                        // 是否有针对功能的规则
                        .eq(AlertRule::getFunctionId, deviceData.getFunctionId())
                        .eq(AlertRule::getStatus, 1)
                        // 是否有针对设备的规则
                        .in(AlertRule::getIotId, "-1", deviceData.getIotId());

                List<AlertRule> list = list(wrapper);

                //6.如果没有对设备报警的有效规则直接结束
            if (ObjectUtil.isEmpty(list)) {
                return;
            }
            list.forEach(alertRule -> deviceDataAlarmHandle(alertRule, deviceData));
            // 如果不在报警规则的生效时间范围内直接结束
        }

        private void deviceDataAlarmHandle(AlertRule alertRule, DeviceData deviceData) {
            //7.如果设备上报的时间数据不在报警规则生效的时间范围内直接结束
            // 获取报警规则的有效时间段配置，格式为"HH:mm:ss~HH:mm:ss"
            String[] split = alertRule.getAlertEffectivePeriod().split("~");
            // 解析开始时间和结束时间
            LocalTime start = LocalTime.parse(split[0]);
            LocalTime end = LocalTime.parse(split[1]);
            // 获取设备数据的报警时间（仅包含时间部分）
            LocalTime time = deviceData.getAlarmTime().toLocalTime();
            // 判断报警时间是否在有效时间段之外，如果在时间段之外则直接返回，不触发报警
            if (time.isBefore(start) || time.isAfter(end)) {
                return;
            }
            // 获取设备上报的数据
            double dataValue = Double.parseDouble(deviceData.getDataValue());

            String countKey = StringUtils.format(CacheConstants.IOT_ALERT_COUNT_KEY,deviceData.getIotId(),alertRule.getId());
           //  如果运算符是<,并且设备上报的数据大于阈值，或者运算符是>=,并且设备上报的数据小于阈值，则直接结束
           if("<".equals(alertRule.getOperator())&&dataValue>alertRule.getValue()
           || ">".equals(alertRule.getOperator())&&dataValue<alertRule.getValue()){
               //  没有异常清除上报的次数
               redisTemplate.delete(countKey);
               return;
           }


           // 8.如果当前处于沉默期就直接结束
           //  如果之前报警了，添加了责任人，就要设置一个沉默标志，有效期就是沉默期
           //  报警前需要查询是否有沉默标志，有就直接结束
           //  将沉默标志存储在redis里，准备一个key包含设备标识和规则标识，存储值随便，这样不同设备之间的沉默期就不会相互影响

           // 构造沉默期标识key，用于判断当前设备在该报警规则下是否处于沉默期
           String silentFlag =StringUtils.format(CacheConstants.IOT_ALERT_SILENT_KEY,deviceData.getIotId(),alertRule.getId());
           // 检查Redis中是否存在该沉默期标识
           // 如果存在，说明该设备在该报警规则下仍处于沉默期内，不需要重复报警
           Boolean isSilent =redisTemplate.hasKey(silentFlag);
           // 如果处于沉默期，则直接返回，不进行后续的报警处理
           if(isSilent){
               return;
           }

            //9.如果异常持续周期没有达到规则上限，就不需要报警直接结束
            // 每次定时任务执行时，发现数据异常就统计异常次数加一，并存储到redis里
            // redis里的key包含设备ID，规则ID，value值，异常次数，redis数字自增一的 string命令 incr key
            // 如果异常次数加一后，没有达到持续周期的报警要求，直接结束
            Long count = redisTemplate.opsForValue().increment(countKey, 1);
            if(count<alertRule.getDuration()){
                return;
           }
            // 确定需要发出警报时，需要清理异常持续周期
                redisTemplate.delete(countKey);
            // 发出警报后需要进入沉默周期添加沉默标志
                redisTemplate.opsForValue().set(silentFlag,"1",alertRule.getAlertSilentPeriod(), TimeUnit.MINUTES);
                Set<Long> userIds=new HashSet<>();
            // 如果是设备异常数据就通知维修工
            if(alertRule.getAlertDataType()==1){
                String roleName = "维修工";

                Set<Long> ids =userRoleMapper.selectUserIdsByRoleName(roleName);

              userIds.addAll(ids);
            }
            // 如果是老人数据异常就通知护理员
             else if (alertRule.getAlertDataType()==0){
            //  如果是随身设备就根据设备ID查找护理员
                if(deviceData.getLocationType()==0){
                    Set<Long> ids = nursingElderMapper.selectNursingIdsByElderId(deviceData.getAccessLocation());
                    userIds.addAll(ids);
                }
            //  如果是固定设备并绑定床位就根据床位设备ID找老人，再根据老人找护理员
              else if(deviceData.getLocationType()==1&&deviceData.getPhysicalLocationType()==2){
                    Set<Long> ids = deviceMapper.selectNurseIdsByBedIotId(deviceData.getIotId());
                    userIds.addAll(ids);
              }
            }

             String roleName = "超级管理员";

             Set<Long> idsRoleName =userRoleMapper.selectUserIdsByRoleName(roleName);
             userIds.addAll(idsRoleName);
            //  保存报警数据
            String reason =deviceData.getFunctionId()+alertRule.getOperator()+alertRule.getValue()+"持续"+alertRule.getDuration()+"周期";
            List<AlertData> list = new ArrayList<>();
            for(Long userId:userIds){
                AlertData alertData =new AlertData();

                BeanUtils.copyProperties(deviceData,alertData);
                alertData.setId(null);

                alertData.setAlertRuleId(alertRule.getId());
                alertData.setAlertReason(reason);
                alertData.setType(alertRule.getAlertDataType());
                alertData.setStatus(0);
                alertData.setUserId(userId);

                list.add(alertData);

            }
            alertDataService.saveBatch(list);
        }
    }
